package com.mapbox.maps.testapp.examples.markersandcallouts.viewannotation

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Bundle
import android.util.TypedValue
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import com.mapbox.bindgen.Expected
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.*
import com.mapbox.maps.extension.style.image.image
import com.mapbox.maps.extension.style.layers.generated.symbolLayer
import com.mapbox.maps.extension.style.layers.properties.generated.IconAnchor
import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
import com.mapbox.maps.extension.style.sources.generated.rasterDemSource
import com.mapbox.maps.extension.style.sources.getSourceAs
import com.mapbox.maps.extension.style.style
import com.mapbox.maps.extension.style.terrain.generated.terrain
import com.mapbox.maps.plugin.gestures.*
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.databinding.ActivityViewAnnotationShowcaseBinding
import com.mapbox.maps.viewannotation.ViewAnnotationManager
import com.mapbox.maps.viewannotation.ViewAnnotationUpdateMode
import com.mapbox.maps.viewannotation.annotatedLayerFeature
import com.mapbox.maps.viewannotation.annotationAnchor
import com.mapbox.maps.viewannotation.viewAnnotationOptions
import java.util.concurrent.CopyOnWriteArrayList

/**
 * Example how to add view annotations to the map.
 *
 * Specifically view annotations will be associated with marker icons
 * showcasing how to implement functionality similar to MarkerView from Maps v9.
 */
class ViewAnnotationShowcaseActivity :
  AppCompatActivity(),
  OnMapClickListener,
  OnMapLongClickListener {

  private lateinit var mapboxMap: MapboxMap
  private lateinit var viewAnnotationManager: ViewAnnotationManager
  private val pointList = CopyOnWriteArrayList<Feature>()
  private var markerId = 0

  private var markerWidth = 0
  private var markerHeight = 0

  private val asyncInflater by lazy { AsyncLayoutInflater(this) }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ActivityViewAnnotationShowcaseBinding.inflate(layoutInflater)
    setContentView(binding.root)

    viewAnnotationManager = binding.mapView.viewAnnotationManager

    val bitmap = ContextCompat.getDrawable(this, R.drawable.ic_blue_marker)!!.toBitmap()
    markerWidth = bitmap.width
    markerHeight = bitmap.height

    mapboxMap = binding.mapView.mapboxMap.apply {
      loadStyle(
        styleExtension = prepareStyle(Style.STANDARD, bitmap)
      ) {
        addOnMapClickListener(this@ViewAnnotationShowcaseActivity)
        addOnMapLongClickListener(this@ViewAnnotationShowcaseActivity)
        binding.fabStyleToggle.setOnClickListener {
          when (style?.styleURI) {
            Style.STANDARD -> loadStyle(prepareStyle(Style.SATELLITE_STREETS, bitmap))
            Style.SATELLITE_STREETS -> loadStyle(prepareStyle(Style.STANDARD_SATELLITE, bitmap))
            Style.STANDARD_SATELLITE -> loadStyle(prepareStyle(Style.STANDARD, bitmap))
          }
        }
        Toast.makeText(this@ViewAnnotationShowcaseActivity, STARTUP_TEXT, Toast.LENGTH_LONG).show()
      }
    }
  }

  override fun onCreateOptionsMenu(menu: Menu): Boolean {
    menuInflater.inflate(R.menu.menu_view_annotation, menu)
    return true
  }

  override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
      R.id.action_view_annotation_fixed_delay -> {
        viewAnnotationManager.setViewAnnotationUpdateMode(ViewAnnotationUpdateMode.MAP_FIXED_DELAY)
        true
      }

      R.id.action_view_annotation_map_synchronized -> {
        viewAnnotationManager.setViewAnnotationUpdateMode(ViewAnnotationUpdateMode.MAP_SYNCHRONIZED)
        true
      }

      else -> super.onOptionsItemSelected(item)
    }
  }

  private fun prepareStyle(styleUri: String, bitmap: Bitmap) = style(styleUri) {
    +image(BLUE_ICON_ID, bitmap)
    +geoJsonSource(SOURCE_ID) {
      featureCollection(FeatureCollection.fromFeatures(pointList))
    }
    if (styleUri == Style.SATELLITE_STREETS) {
      +rasterDemSource(TERRAIN_SOURCE) {
        url(TERRAIN_URL_TILE_RESOURCE)
      }
      +terrain(TERRAIN_SOURCE)
    }
    +symbolLayer(LAYER_ID, SOURCE_ID) {
      iconImage(BLUE_ICON_ID)
      iconAnchor(IconAnchor.BOTTOM)
      iconAllowOverlap(false)
    }
  }

  override fun onMapLongClick(point: Point): Boolean {
    val markerId = addMarkerAndReturnId(point)
    addViewAnnotation(point, markerId)
    return true
  }

  override fun onMapClick(point: Point): Boolean {
    mapboxMap.queryRenderedFeatures(
      RenderedQueryGeometry(mapboxMap.pixelForCoordinate(point)),
      RenderedQueryOptions(listOf(LAYER_ID), null)
    ) {
      onFeatureClicked(it) { feature ->
        feature.id()?.let { featureId ->
          viewAnnotationManager.getViewAnnotation(
            AnnotatedLayerFeature.Builder()
              .layerId(LAYER_ID)
              .featureId(featureId)
              .build()
          )?.toggleViewVisibility()
        }
      }
    }
    return true
  }

  private fun onFeatureClicked(
    expected: Expected<String, List<QueriedRenderedFeature>>,
    onFeatureClicked: (Feature) -> Unit
  ) {
    if (expected.isValue && expected.value?.size!! > 0) {
      expected.value?.get(0)?.queriedFeature?.feature?.let { feature ->
        onFeatureClicked.invoke(feature)
      }
    }
  }

  private fun View.toggleViewVisibility() {
    visibility = if (visibility == View.VISIBLE) View.GONE else View.VISIBLE
  }

  private fun addMarkerAndReturnId(point: Point): String {
    val currentId = "$MARKER_ID_PREFIX${(markerId++)}"
    pointList.add(Feature.fromGeometry(point, null, currentId))
    val featureCollection = FeatureCollection.fromFeatures(pointList)
    mapboxMap.getStyle { style ->
      style.getSourceAs<GeoJsonSource>(SOURCE_ID)?.featureCollection(featureCollection)
    }
    return currentId
  }

  @SuppressLint("SetTextI18n")
  private fun addViewAnnotation(point: Point, featureId: String) {
    viewAnnotationManager.addViewAnnotation(
      resId = R.layout.item_callout_view,
      options = viewAnnotationOptions {
        annotatedLayerFeature(LAYER_ID) {
          featureId(featureId)
        }
        annotationAnchor {
          anchor(ViewAnnotationAnchor.BOTTOM)
        }
        allowOverlap(false)
      },
      asyncInflater = asyncInflater
    ) { viewAnnotation ->
      viewAnnotation.visibility = View.GONE
      // calculate offsetY manually taking into account icon height only because of bottom anchoring
      viewAnnotationManager.updateViewAnnotation(
        viewAnnotation,
        viewAnnotationOptions {
          annotationAnchor {
            anchor(ViewAnnotationAnchor.BOTTOM)
            offsetY(markerHeight.toDouble())
          }
        }
      )
      viewAnnotation.findViewById<TextView>(R.id.textNativeView).text =
        "lat=%.2f\nlon=%.2f".format(point.latitude(), point.longitude())
      viewAnnotation.findViewById<ImageView>(R.id.closeNativeView).setOnClickListener { _ ->
        viewAnnotationManager.removeViewAnnotation(viewAnnotation)
      }
      viewAnnotation.findViewById<Button>(R.id.selectButton).setOnClickListener { b ->
        val button = b as Button
        val isSelected = button.text.toString().equals("SELECT", true)
        val pxDelta =
          (if (isSelected) SELECTED_ADD_COEF_DP.dpToPx() else -SELECTED_ADD_COEF_DP.dpToPx()).toInt()
        button.text = if (isSelected) "DESELECT" else "SELECT"
        viewAnnotationManager.updateViewAnnotation(
          viewAnnotation,
          viewAnnotationOptions {
            selected(isSelected)
          }
        )
        (button.layoutParams as ViewGroup.MarginLayoutParams).apply {
          bottomMargin += pxDelta
          rightMargin += pxDelta
          leftMargin += pxDelta
        }
        button.requestLayout()
      }
    }
  }

  private fun Float.dpToPx() = TypedValue.applyDimension(
    TypedValue.COMPLEX_UNIT_DIP,
    this,
    this@ViewAnnotationShowcaseActivity.resources.displayMetrics
  )

  private companion object {
    const val BLUE_ICON_ID = "blue"
    const val SOURCE_ID = "source_id"
    const val LAYER_ID = "layer_id"
    const val TERRAIN_SOURCE = "TERRAIN_SOURCE"
    const val TERRAIN_URL_TILE_RESOURCE = "mapbox://mapbox.mapbox-terrain-dem-v1"
    const val MARKER_ID_PREFIX = "view_annotation_"
    const val SELECTED_ADD_COEF_DP: Float = 8f
    const val STARTUP_TEXT =
      "Long click on a map to add a marker and click on a marker to pop-up annotation."
  }
}